package BusinessLogic;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import DatabasePackage.DatabaseManager;
import DatabasePackage.DatabaseManager.DBTables;
import DatabasePackage.ItemAlreadyInDBException;
import DatabasePackage.UserCoursesDBTable;
import DatabasePackage.UserDBTable;
import DatabasePackage.UserDBTable.UserDBTScheme;

/**
 * 		   UserActionManager class includes all of the actions
 *         relevant to users in the system. It extends the base ActionManager
 *         class and implements all of the relevant actions.
 */
public class UserManager extends ActionManager {
	public enum Fields{
		adminkey, me
	}
	
	public enum UserType{
		student, admin
	}
	
	private final String adminKey = "31nohackinghere13(";
  public UserManager(DatabaseManager dbm) {
    super(dbm);
  }
  
  @Action public Result updateUserDetails(Map<String, String[]> params)
  {
	  Connection con = null;
	  Result res = null;
	  try
	  {
		  List<String> l = new ArrayList<String>(Arrays.asList(UserDBTScheme.USER_NAME.toString(), UserDBTScheme.FIRST_NAME.toString(), UserDBTScheme.LAST_NAME.toString(),
				  UserDBTScheme.EMAIL.toString(),UserDBTScheme.PHONE.toString(),UserDBTScheme.PASSWORD.toString(),UserDBTScheme.ROLE.toString()));
		  validateParams(params, l);
		  UserDBTable table = (UserDBTable) dbMng.getTable(DBTables.USER);
		  con = table.connect();
		  User old = table.getUser(con, getStringParam(params,UserDBTScheme.USER_NAME.toString()));
		  if (getStringParam(params, UserDBTScheme.PASSWORD.toString()).isEmpty())
			  params.put(UserDBTScheme.PASSWORD.toString(), new String[]{old.getPass()});
		  res = validateUserDetails(params);
		  if (((Map<String,String[]>)res.getFailureReturnValue()).get("error")!=null) return res;
		  con.setAutoCommit(false);
		  try
		  {
			  String pass = old.getPass();
			  if (!getStringParam(params, UserDBTScheme.PASSWORD.toString()).isEmpty())
				  pass = getStringParam(params, UserDBTScheme.PASSWORD.toString());
			  User user = new User(old.getName(), pass, getStringParam(params, UserDBTScheme.FIRST_NAME.toString()),
					  getStringParam(params, UserDBTScheme.LAST_NAME.toString()), getStringParam(params, UserDBTScheme.EMAIL.toString()),
					  null, old.getRole(),getStringParam(params, UserDBTScheme.PHONE.toString()));
			  table.updateUser(con, user);
			  con.commit();
		  }
		  catch (SQLException e)
		  {
			  con.rollback();
			  return updateUserDetails(params);
		  }
		  return createSuccessResult(null);
	  }
	  catch(Exception e)
	  {
		  
		 Map<String,String[]> p = new HashMap<String, String[]>();
		 p.put("error", new String[]{e.getMessage()});
		 return createFailureResult(p);
	  }
	  finally{
			safeCloseConnection(con);
		} 
  }
  
  
  private Result validateUserDetails(Map<String, String[]> params) {
	Map<String,String[]> $ = new HashMap<String, String[]>();
	boolean error = false;
	String uname = getStringParam(params, UserDBTScheme.USER_NAME.toString());
	String email = getStringParam(params, UserDBTScheme.EMAIL.toString());
	String fname = getStringParam(params, UserDBTScheme.FIRST_NAME.toString());
	String lname = getStringParam(params, UserDBTScheme.LAST_NAME.toString());
	String pass = getStringParam(params, UserDBTScheme.PASSWORD.toString());
	String phone = getStringParam(params, UserDBTScheme.PHONE.toString());
	$.put(UserDBTScheme.USER_NAME.toString(), new String[]{uname,""});
	$.put(UserDBTScheme.EMAIL.toString(), new String[]{email,""});
	$.put(UserDBTScheme.FIRST_NAME.toString(), new String[]{fname,""});
	$.put(UserDBTScheme.LAST_NAME.toString(), new String[]{lname,""});
	$.put(UserDBTScheme.PASSWORD.toString(), new String[]{pass,""});
	$.put(UserDBTScheme.PHONE.toString(), new String[]{phone,""});
	if (uname.isEmpty())
	{
		$.get(UserDBTScheme.USER_NAME.toString())[1] = "User name cannot be empty";
		error = true;
	}
	if (uname.length() > 255)
	{
		$.get(UserDBTScheme.USER_NAME.toString())[1] = "User name cannot exceed 255 chars";
		error = true;
	}
	if (email.length() > 255)
	{
		$.get(UserDBTScheme.EMAIL.toString())[1] = "Email cannot exceed 255 chars";
		error = true;
	}
	Pattern pattern = Pattern.compile("^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$");
	Matcher matcher = pattern.matcher(email);
	if ((!email.isEmpty()) && !matcher.matches())
	{
		$.get(UserDBTScheme.EMAIL.toString())[1] = "Incorrect email address";
		error = true;
	}
	if (pass.isEmpty())
	{
		$.get(UserDBTScheme.PASSWORD.toString())[1] = "Password cannot be empty";
		error = true;
	}
	if (pass.length() > 255)
	{
		$.get(UserDBTScheme.PASSWORD.toString())[1] = "Password cannot exceed 255 chars";
		error = true;
	}
	if (fname.length() > 255)
	{
		$.get(UserDBTScheme.FIRST_NAME.toString())[1] = "First name cannot exceed 255 chars";
		error = true;
	}
	if (phone.length() > 30)
	{
		$.get(UserDBTScheme.PHONE.toString())[1] = "Phone number cannot exceed 30 chars";
		error = true;
	}
	if (!isPhoneOrEmpty(phone))
	{
		$.get(UserDBTScheme.PHONE.toString())[1] = "Invalid phone number";
		error = true;
	}
	if (lname.length() > 255)
	{
		$.get(UserDBTScheme.LAST_NAME.toString())[1] = "Last name cannot exceed 255 chars";
		error = true;
	}
	if (!error)
		{
			$.put("error", null);
		}
	else
	{
		$.put("error", new String[]{});
	}
	return createFailureResult($);
}

  private boolean isPhoneOrEmpty(String str) {
      
      //It can't contain only numbers if it's null or empty...
      if (str.length() == 0)
          return true;
      
      for (int i = 0; i < str.length(); i++) {

          //If we find a non-digit character we return false.
          if (!Character.isDigit(str.charAt(i)))
              return false;
      }
      
      return true;
  }
@Action public Result getAllUsers(Map<String, String[]> params)
  {
	  Connection con = null;
	  try
	  {
		  UserDBTable table = (UserDBTable) dbMng.getTable(DBTables.USER);
		  con = table.connect();
		  List<User> userList = table.getAllUsers(con);
		  for (User user : userList)
		  {
			  user.setPass(null);
		  }
		  return createSuccessResult(new UserContainer(userList));
	  }
	  catch(Exception e)
	  {
		  return createFailureResult("An error occured");
	  }
	  finally{
			safeCloseConnection(con);
	  }
	  
  }

@Action public Result getUser(Map<String, String[]> params)
{
	  List<String> l = new ArrayList<String>(Arrays.asList(UserDBTScheme.USER_NAME.toString(),Fields.me.toString()));
	  validateParams(params, l);
	  Connection con = null;
	  try
	  {
		  UserDBTable table = (UserDBTable) dbMng.getTable(DBTables.USER);
		  con = table.connect();
		  User user = table.getUser(con, getStringParam(params,UserDBTScheme.USER_NAME.toString()));
		  User me = table.getUser(con, getStringParam(params,Fields.me.toString()));
		  if (!me.getName().equals(user.getName()))
			  user.setPass("");
		  return createSuccessResult(user);
	  }
	  catch(Exception e)
	  {
		  return createFailureResult("An error occured");
	  }
	  finally{
			safeCloseConnection(con);
	  }
	  
}

@Action public Result deleteUser(Map<String, String[]> params)
{
	  List<String> l = new ArrayList<String>(Arrays.asList(UserDBTScheme.USER_NAME.toString(),Fields.me.toString()));
	  validateParams(params, l);
	  Connection con = null;
	  String username = getStringParam(params,UserDBTScheme.USER_NAME.toString());
	  try
	  {
		  UserDBTable table = (UserDBTable) dbMng.getTable(DBTables.USER);
		  con = table.connect();
		  User me = table.getUser(con, getStringParam(params,Fields.me.toString()));
		  if (!me.getRole().equals(UserType.admin.toString()))
			  return createFailureResult("Only admin can delete users");
		  User user = table.getUser(con, username);
		  if (user.getRole().equals("admin"))
			  return createFailureResult("Deleting admins is not allowed");
		  con.setAutoCommit(false);
		  table.deleteUser(con, username);
		  //List<String> courses = coursetable.getUserCourses(con, username);
		  //for (String course : courses)
			//  coursetable.deleteUserCourseTuple(con, course, username);
		  try {
			  con.commit();
		  }
		  catch(SQLException e)
		  {
			  con.rollback();
			  return deleteUser(params);
		  }
		  return createSuccessResult(null);
	  }
	  catch(Exception e)
	  {
		  return createFailureResult(e.getMessage());
	  }
	  finally{
			safeCloseConnection(con);
	  }
}
  @Action public Result createNewUser(Map<String, String[]> params) {
	  Connection con = null;
	  try
	  {
		  List<String> l = new ArrayList<String>(Arrays.asList(UserDBTScheme.USER_NAME.toString(), UserDBTScheme.FIRST_NAME.toString(), UserDBTScheme.LAST_NAME.toString(),
				  UserDBTScheme.EMAIL.toString(),UserDBTScheme.PASSWORD.toString(),UserDBTScheme.ROLE.toString(),
				  UserDBTScheme.PHONE.toString()));
		  validateParams(params, l);
		  Result res = validateUserDetails(params);
		  if (((Map<String,String[]>)res.getFailureReturnValue()).get("error")!=null) return res;
		  UserDBTable table = (UserDBTable) dbMng.getTable(DBTables.USER);
		  con = table.connect();
		  con.setAutoCommit(false);
		  try {
			  if (getStringParam(params, UserDBTScheme.ROLE.toString()).equals(UserType.student.toString()))
			  {
				  User user = new User(getStringParam(params,UserDBTScheme.USER_NAME.toString()), getStringParam(params, UserDBTScheme.PASSWORD.toString()),
						  getStringParam(params, UserDBTScheme.FIRST_NAME.toString()), getStringParam(params, UserDBTScheme.LAST_NAME.toString())
						  , getStringParam(params, UserDBTScheme.EMAIL.toString()), null, "student",
						  getStringParam(params, UserDBTScheme.PHONE.toString()));
				  table.insertUser(con, user);
			  }
			  else if (getStringParam(params, UserDBTScheme.ROLE.toString()).equals(UserType.admin.toString()))
			  {
				  if (params.containsKey(Fields.adminkey.toString())&&params.get(Fields.adminkey.toString()).length==1 &&
						  getStringParam(params, Fields.adminkey.toString()).equals(adminKey.toString()))
				  {
					  User user = new User(getStringParam(params,UserDBTScheme.USER_NAME.toString()), getStringParam(params, UserDBTScheme.PASSWORD.toString()),
							  getStringParam(params, UserDBTScheme.FIRST_NAME.toString()), getStringParam(params, UserDBTScheme.LAST_NAME.toString())
							  , getStringParam(params, UserDBTScheme.EMAIL.toString()), null, "admin",
							  getStringParam(params, UserDBTScheme.PHONE.toString()));
					  table.insertUser(con, user);
				  }
				  else {
					  ((Map<String,String[]>)res.getFailureReturnValue()).put("error",new String[]{"admin requires a correct admin key"});
					  return res;
				  }
			  }
			  else
			  {
				  return createFailureResult("invalid user type requested");
			  }
		  } catch (ItemAlreadyInDBException e) {
			  ((Map<String,String[]>)res.getFailureReturnValue()).put("error",new String[]{"user already exists"});
			  return res;
		  }
		  try{
			  con.commit();
			 }
			 catch (SQLException e)
			 {
				 con.rollback();
				 return createNewUser(params);
			 }
	return createSuccessResult(null);
	}catch(Exception e)
	  	{
		  return createFailureResult(e.getMessage());
	  	}
	  	finally
	  		{
				safeCloseConnection(con);
	  }
	  
  }
  

}
